package org.ehe.finance.payable.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ehe.common.core.exception.ServiceException;
import org.ehe.common.mybatis.core.page.PageQuery;
import org.ehe.common.mybatis.core.page.TableDataInfo;
import org.ehe.common.satoken.utils.LoginHelper;
import org.ehe.erp.domain.finance.ErpFinancePayment;
import org.ehe.erp.mapper.finance.ErpFinancePaymentMapper;
import org.ehe.erp.service.finance.impl.ErpFinancePaymentServiceImpl;
import org.ehe.finance.payable.config.PayableConfig;
import org.ehe.finance.payable.constant.PayableConstants;
import org.ehe.finance.payable.domain.ErpFinancePayable;
import org.ehe.finance.payable.domain.bo.PayableBo;
import org.ehe.finance.payable.domain.vo.PayableResponseVO;
import org.ehe.finance.payable.mapper.PayableMapper;
import org.ehe.finance.payable.publisher.PayableEventPublisher;
import org.ehe.finance.payable.service.PayableService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 应付账款主表
 * @author :zhangnn
 * @className :PayableServiceImpl
 * @description: TODO
 * @date 2025-09-02 10:11:53
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PayableServiceImpl extends ServiceImpl<PayableMapper, ErpFinancePayable> implements PayableService {
    private final PayableConfig payableConfig;
    private final PayableEventPublisher eventPublisher;

    private final ErpFinancePaymentMapper paymentMapper;

    /**
     * 分页
     * @param bo
     * @param pageQuery
     * @return
     */
    @Override
    public TableDataInfo<PayableResponseVO> selectPayablePage(PayableBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<ErpFinancePayable> lqw = buildQueryWrapper(bo);
        IPage<PayableResponseVO> page = this.baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(page);
    }
    /**
     * 当付款单状态变更（如从草稿变为已支付）时，调用此方法更新对应应付账款的到期日
     * @param paymentId 付款单ID
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateDueDateAfterPaymentStatusChange(Long paymentId) {
        // 1. 查询该付款单信息
        ErpFinancePayment payment = paymentMapper.selectById(paymentId);
        if (Objects.isNull(payment)) {
            log.warn("付款单不存在，ID：{}", paymentId);
            return;
        }
        Long payableId = payment.getPayableId();
        if (Objects.isNull(payableId)) {
            log.warn("付款单未关联应付账款，ID：{}", paymentId);
            return;
        }

        // 2. 查询该应付账款关联的所有付款单（按批次序号排序）
        List<ErpFinancePayment> paymentList = paymentMapper.selectByPayableId(payableId);
        if (CollectionUtils.isEmpty(paymentList)) {
            log.warn("应付账款未关联任何付款单，ID：{}", payableId);
            return;
        }
        List<ErpFinancePayment> sortedPayments = paymentList.stream()
            .sorted(Comparator.comparingLong(ErpFinancePayment::getBatchId)) // 按批次序号升序
            .collect(Collectors.toList());

        // 3. 重新计算到期日：取第一个未支付批次的payment_time
        LocalDateTime newDueDate = null;
        for (ErpFinancePayment p : sortedPayments) {
            // 未支付状态判断（根据实际枚举值调整）
            if (p.getStatus() == ErpFinancePaymentServiceImpl.PaymentStatus.DRAFT ||
                p.getStatus() == ErpFinancePaymentServiceImpl.PaymentStatus.UNAUDITED) {
                newDueDate = p.getPaymentTime();
                break; // 找到第一个未支付批次，跳出循环
            }
        }

        // 4. 若所有批次均已支付，到期日设为最后一个批次的付款时间
        if (Objects.isNull(newDueDate)) {
            newDueDate = sortedPayments.get(sortedPayments.size() - 1).getPaymentTime();
        }

        // 5. 更新应付账款的到期日
        ErpFinancePayable payable = this.getById(payableId);
        if (Objects.nonNull(payable)) {
            payable.setDueDate(newDueDate.toLocalDate());
            this.updateById(payable);
            log.info("应付账款到期日已更新，ID：{}，新到期日：{}", payableId, newDueDate);
        }
    }

    /**
     * 创建应付账款
     * @param payableDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean createPayable(PayableBo payableDTO) {
        // 数据校验
        validatePayableData(payableDTO);

        // 检查单据号是否重复
        checkBillNoExists(payableDTO.getBillNo(), null);

        // 创建实体对象
        ErpFinancePayable payable = new ErpFinancePayable();
        BeanUtils.copyProperties(payableDTO, payable);

        // 设置默认值（使用配置）
        payable.setPaidAmount(payableDTO.getPaidAmount() != null ?
            payableDTO.getPaidAmount() : BigDecimal.ZERO);
        payable.setUnpaidAmount(payableDTO.getTotalAmount().subtract(payable.getPaidAmount()));
        payable.setExchangeRate(payableDTO.getExchangeRate() != null ?
            payableDTO.getExchangeRate() : new BigDecimal(PayableConstants.DEFAULT_EXCHANGE_RATE));

        // 使用配置中的默认币种
        if (payable.getCurrencyId() == null) {
            payable.setCurrencyId(PayableConstants.DEFAULT_CURRENCY_ID);
        }

        // 设置结算状态
        payable.setSettlementStatus(calculateSettlementStatus(
            payable.getTotalAmount(), payable.getPaidAmount()));

        // 设置租户ID
        payable.setTenantId(LoginHelper.getTenantId());

        //查询是否存在当前支付订单信息
        boolean result = false;
        ErpFinancePayable erpFinancePayable = this.baseMapper.selectOne(new LambdaQueryWrapper<ErpFinancePayable>()
            .eq(ErpFinancePayable::getSourceBillNo, payableDTO.getSourceBillNo()));
        if(Objects.nonNull(erpFinancePayable)){
            payableDTO.setId(erpFinancePayable.getId());
            result = this.updatePayable(payableDTO);
        } else {
            result = this.save(payable);

        }
        if (result) {
            log.info("创建应付账款成功，单据号: {}", payable.getBillNo());

            // 发布应付账款创建事件
            eventPublisher.publishPayableCreated(
                payable.getId(),
                payable.getBillNo(),
                payable.getSupplierId(),
                payable.getTotalAmount()
            );
        }

        return result;
    }

    /**
     * 修改应付账款
     * @param payableDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updatePayable(PayableBo payableDTO) {
        if (payableDTO.getId() == null) {
            throw new ServiceException("应付账款ID不能为空");
        }

        // 数据校验
        validatePayableData(payableDTO);

        // 检查单据号是否重复
        checkBillNoExists(payableDTO.getBillNo(), payableDTO.getId());

        // 查询原数据
        ErpFinancePayable existPayable = this.getById(payableDTO.getId());
        if (existPayable == null) {
            throw new ServiceException("应付账款不存在");
        }

        // 更新数据
        BeanUtils.copyProperties(payableDTO, existPayable);
        existPayable.setUnpaidAmount(existPayable.getTotalAmount().subtract(
            existPayable.getPaidAmount() != null ? existPayable.getPaidAmount() : BigDecimal.ZERO));
        existPayable.setSettlementStatus(calculateSettlementStatus(
            existPayable.getTotalAmount(), existPayable.getPaidAmount()));

        boolean result = this.updateById(existPayable);
        if (result) {
            log.info("更新应付账款成功，单据号: {}", existPayable.getBillNo());
        }

        return result;
    }

    /**
     * 删除应付账款
     * @param id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deletePayable(Long id) {
        ErpFinancePayable payable = this.getById(id);
        if (payable == null) {
            throw new ServiceException("应付账款不存在");
        }

        // 检查是否可以删除
        if (payable.getPaidAmount().compareTo(BigDecimal.ZERO) > 0) {
            throw new ServiceException("该应付账款已有付款记录，不能删除");
        }

        boolean result = this.removeById(id);
        if (result) {
            log.info("删除应付账款成功，单据号: {}", payable.getBillNo());
        }

        return result;
    }

    /**
     * 批量删除应付账款
     * @param ids
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDeletePayable(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("删除ID列表不能为空");
        }

        // 检查是否可以删除
        List<ErpFinancePayable> payables = this.listByIds(ids);
        for (ErpFinancePayable payable : payables) {
            if (payable.getPaidAmount().compareTo(BigDecimal.ZERO) > 0) {
                throw new ServiceException("单据号 " + payable.getBillNo() + " 已有付款记录，不能删除");
            }
        }

        boolean result = this.removeByIds(ids);
        if (result) {
            log.info("批量删除应付账款成功，删除数量: {}", ids.size());
        }

        return result;
    }

    /**
     * 根据ID查询应付账款详情
     * @param id
     * @return
     */
    @Override
    public ErpFinancePayable getPayableDetail(Long id) {
        ErpFinancePayable payable = this.getById(id);
        if (payable != null) {
            setDisplayNames(payable);
        }
        return payable;
    }

    /**
     * 更新付款金额
     * @param payableId
     * @param paymentAmount
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updatePaymentAmount(Long payableId, BigDecimal paymentAmount) {
        if (paymentAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("付款金额必须大于0");
        }

        // 检查付款精度
        if (paymentAmount.scale() > payableConfig.getPayment().getPaymentPrecision()) {
            throw new ServiceException("付款金额小数位数超出限制");
        }

        // 检查最小付款金额
        BigDecimal minAmount = new BigDecimal(payableConfig.getPayment().getMinPaymentAmount());
        if (paymentAmount.compareTo(minAmount) < 0) {
            throw new ServiceException("付款金额不能小于最小付款金额: " + minAmount);
        }

        ErpFinancePayable payable = this.getById(payableId);
        if (payable == null) {
            throw new ServiceException("应付账款不存在");
        }

        // 检查是否允许超额付款
        BigDecimal newPaidAmount = payable.getPaidAmount().add(paymentAmount);
        if (!payableConfig.getPayment().isAllowOverPayment() &&
            newPaidAmount.compareTo(payable.getTotalAmount()) > 0) {
            throw new ServiceException("付款金额超出应付金额，不允许超额付款");
        }

        String tenantId = LoginHelper.getTenantId();
        int result = baseMapper.updatePaymentAmount(payableId, paymentAmount, tenantId);

        if (result > 0) {
            log.info("更新应付账款付款金额成功，ID: {}, 付款金额: {}", payableId, paymentAmount);

            // 计算剩余金额
            BigDecimal remainingAmount = payable.getTotalAmount().subtract(newPaidAmount);

            // 发布付款事件
            eventPublisher.publishPaymentMade(
                payableId,
                payable.getBillNo(),
                paymentAmount,
                remainingAmount
            );
        }

        return result > 0;
    }

    /**
     * 查询即将到期的应付账款
     * @param days
     * @return
     */
    @Override
    public List<ErpFinancePayable> getDuePayables(int days) {
        String tenantId = LoginHelper.getTenantId();
        LocalDate startDate = LocalDate.now();
        LocalDate endDate = LocalDate.now().plusDays(days);

        List<ErpFinancePayable> payables = baseMapper.selectDuePayables(tenantId, startDate, endDate);
        payables.forEach(this::setDisplayNames);

        return payables;
    }

    /**
     * 根据供应商统计应付金额
     * @return
     */
    @Override
    public List<PayableMapper.PayableStatistics> getPayableStatisticsBySupplier() {
        String tenantId = LoginHelper.getTenantId();
        return baseMapper.statisticsBySupplier(tenantId);
    }

    /**
     * 生成应付账款单据号
     * @return
     */
    @Override
    public String generateBillNo() {
        // 使用配置中的单据号生成规则
        PayableConfig.BillNoConfig billNoConfig = payableConfig.getBillNo();
        String prefix = billNoConfig.getPrefix();
        String datePart = LocalDate.now().format(DateTimeFormatter.ofPattern(billNoConfig.getDateFormat()));

        // 查询当天最大序号
        String tenantId = LoginHelper.getTenantId();

        // 如果按租户分别编号
        LambdaQueryWrapper<ErpFinancePayable> wrapper = new LambdaQueryWrapper<>();
        wrapper.likeRight(ErpFinancePayable::getBillNo, prefix + datePart)
            .orderByDesc(ErpFinancePayable::getBillNo)
            .last("LIMIT 1");

        if (billNoConfig.isSeparateByTenant()) {
            wrapper.eq(ErpFinancePayable::getTenantId, tenantId);
        }

        ErpFinancePayable lastPayable = this.getOne(wrapper);
        int sequence = 1;

        if (lastPayable != null && StringUtils.isNotEmpty(lastPayable.getBillNo())) {
            String lastBillNo = lastPayable.getBillNo();
            int prefixDateLength = prefix.length() + billNoConfig.getDateFormat().length();
            if (lastBillNo.length() >= prefixDateLength + billNoConfig.getSequenceLength()) {
                try {
                    sequence = Integer.parseInt(lastBillNo.substring(prefixDateLength)) + 1;
                } catch (NumberFormatException e) {
                    log.warn("解析单据号序号失败: {}", lastBillNo);
                }
            }
        }

        String sequenceFormat = "%0" + billNoConfig.getSequenceLength() + "d";
        return prefix + datePart + String.format(sequenceFormat, sequence);
    }

    @Override
    public void validatePayableData(PayableBo payableDTO) {
        if (payableDTO == null) {
            throw new ServiceException("应付账款数据不能为空");
        }

        if (payableDTO.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("应付总金额必须大于0");
        }

        if (payableDTO.getPaidAmount() != null &&
            payableDTO.getPaidAmount().compareTo(payableDTO.getTotalAmount()) > 0) {
            throw new ServiceException("已付款金额不能大于应付总金额");
        }

        if (payableDTO.getDueDate() != null &&
            payableDTO.getDueDate().isBefore(payableDTO.getBillDate())) {
            throw new ServiceException("到期日期不能早于单据日期");
        }
    }

    /**
     * 构建查询条件
     */
    private LambdaQueryWrapper<ErpFinancePayable> buildQueryWrapper(PayableBo bo) {
        LambdaQueryWrapper<ErpFinancePayable> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ErpFinancePayable::getTenantId, LoginHelper.getTenantId());

        if (bo != null) {
            wrapper.like(StringUtils.isNotBlank(bo.getBillNo()), ErpFinancePayable::getBillNo, bo.getBillNo())
                .eq(bo.getSupplierId() != null, ErpFinancePayable::getSupplierId, bo.getSupplierId())
                .eq(bo.getBillType() != null, ErpFinancePayable::getBillType, bo.getBillType())
                .eq(bo.getDepartmentId() != null, ErpFinancePayable::getDepartmentId, bo.getDepartmentId())
                .eq(bo.getPurchaserId() != null, ErpFinancePayable::getPurchaserId, bo.getPurchaserId());
        }

        return wrapper;
    }

    /**
     * 检查单据号是否存在
     */
    private void checkBillNoExists(String billNo, Long excludeId) {
        LambdaQueryWrapper<ErpFinancePayable> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ErpFinancePayable::getBillNo, billNo)
            .eq(ErpFinancePayable::getTenantId, LoginHelper.getTenantId());

        if (excludeId != null) {
            wrapper.ne(ErpFinancePayable::getId, excludeId);
        }

        if (this.count(wrapper) > 0) {
            throw new ServiceException("单据号 " + billNo + " 已存在");
        }
    }

    /**
     * 计算结算状态
     */
    private Integer calculateSettlementStatus(BigDecimal totalAmount, BigDecimal paidAmount) {
        if (paidAmount == null || paidAmount.compareTo(BigDecimal.ZERO) == 0) {
            return PayableConstants.SettlementStatus.UNSETTLED;
        } else if (paidAmount.compareTo(totalAmount) >= 0) {
            return PayableConstants.SettlementStatus.SETTLED;
        } else {
            return PayableConstants.SettlementStatus.PARTIAL;
        }
    }

    /**
     * 设置显示名称
     */
    private void setDisplayNames(ErpFinancePayable payable) {
        // 设置单据类型名称
        if (payable.getBillType() != null) {
            switch (payable.getBillType()) {
                case PayableConstants.BillType.PURCHASE_INVOICE -> payable.setBillTypeName("采购发票");
                case PayableConstants.BillType.OTHER_PAYABLE -> payable.setBillTypeName("其他应付");
                default -> payable.setBillTypeName("未知类型");
            }
        }

        // 设置结算状态名称
        if (payable.getSettlementStatus() != null) {
            switch (payable.getSettlementStatus()) {
                case PayableConstants.SettlementStatus.UNSETTLED -> payable.setSettlementStatusName("未结算");
                case PayableConstants.SettlementStatus.PARTIAL -> payable.setSettlementStatusName("部分结算");
                case PayableConstants.SettlementStatus.SETTLED -> payable.setSettlementStatusName("已结算");
                default -> payable.setSettlementStatusName("未知状态");
            }
        }
    }
}
